§ Fox3.0 插件开发指南(IOS)

§ 介绍

移动平台使用osgi机制管理插件,以module为功能单位,每个moduel可包含三类文件,分别为boot插件、plugin插件和配置文件,其中boot插件是在平台启动和关闭是调用,如版本更新,缓存清理等,而plugin类型插件用于给javascript提供native能力,如拍照,文件上传下载等,而配置文件则是插件的注册信息。如下图
module_ios.png-15.6kB

§ 工程结构说明

§ 目录结构

ios/fox为主工程 Ios/sdk 保留目录,目前暂时不放东西
ios/sdk-core 基础framework库的目录,如logger、通讯、im等
Ios/sdk-three 第三方库的目录,如支付、ocr、二代证等

原则上尽量一个插件代表一个功能,这样有利于后面我们根据需求进行裁剪。

§ 版本文件目录

fox/version 工程资源目录
--------/bundle identifier/configuration/client.properties 配置文件
--------/bundle identifier/tab_images foot tab图标
--------/bundle identifier/workspace web工程目录

PS:bundle identifier中点替换为下划线,例如 fox.app -> fox_app

§ 基础插件目录

fox/app/plugins 为app内置的基础插件目录
内含插件列表:

  • bus 共享存储
  • comm 通信
  • device 外设代理
  • file 文件访问
  • lifecycle APP生命周期
  • logger 日志
  • natives native接口
  • navigate 导航
  • session session插件
  • splash splash插件
  • system 系统native集合
  • version 版本控制
  • whitelist 白名单

§ 扩展插件目录

fox/plugins 为app的扩展插件目录

PS:我们开发的插件,绝大多数应该在该目录下,如IM、定位等

§ swift oc桥接文件

(如果OC的接口需要在swift中引用,则需要在上面#import,OC内部的调用则不需要)
fox/app-Bridging-Header.h

§ Boot插件开发

主要是用于APP启动过程中调用,我们一般用于做一些初始化工作,如过期文件清理,版本更新等。

§ swift 代码


    //
    //  VersionReady.swift
    //  app
    //
    //  Created by 江成 on 2019/8/9.
    //
    
    import Foundation
    
    import fox_ninetales
    import core
    
    //版本准备
    class VersionReady: FXBoot{
        
        /**
         * 启动
         * activity onCreate方法中调用,在所有boot模块done后启动web view
         * 任务完成后必须调用done或cancel方法
         */
        override open func start(interface:FXInterface, context:FXProgressContext){
            //分发任务
            let subContexts = context.distribute(ratios: [20,80])
            //版本发布
            VersionRelease.instance.release(progressContext: subContexts[0])
            //版本更新
            VersionUpdater.instance.update(context: subContexts[1])
        }
        
        /**
         * 关闭
         * activity onDestroy方法中调用,在FXPlugin的onDestroy方法后面
         */
        override open func stop(interface:FXInterface?){
            
        }
        
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

§ 配置文件

配置文件需要手动在模块所在的目录下,新建xml配置文件,文件名的规则fox_extesion_xxx,例如我在模块core_ext_demo中的配置文件的名为fox_extension_core_ext_demo.xml。

     <?xml version="1.0" encoding="UTF-8"?>
    <plugins>
        <!--启动扩展-->
        <extension point="fox.extension.boot">
            <!--版本准备-->
            <boot name="version" class="VersionReady" text="版本部署">
            </boot>
        </extension>
    </plugins>
1
2
3
4
5
6
7
8
9

§ Plugin插件

根据功能和范围插件主要分为APlugin,Device和Native几类,其中Device插件用于调用系统或第三方提供的外设模块,如相机、OCR、二代证等,而Native则用于集成除外设类之外的原生功能,如定位、网络等,而AProxy则用于功能更为复杂的原生功能调用。我们在原生开发的过程中选择优先顺序是Native&Device > AProxy,也是我们根据功能分类优先选择Native或Device,当两个不合适的情况下才选择AProy。下图是他们之间的关系

plugins_ios_3.png-75.4kB

§ Device开发指南

Device插件用于集成外设调用,下面是例子

§ swift

    //
    //  DemoOCRDevice.swift
    //  app
    //
    //  Created by 江成 on 2020/1/2.
    //
    
    import Foundation
    import core
    
    //Demo OCR Deivce
    class DemoOCRDevice: Device{
        
        //外设调用
        public override func call(type:String, action:String, param:String, cite:Cite, callback:@escaping(_ code:Int, _ data:Any?)->Void){
            //根据js传递过来的动作执行
            if action == "test"{
                callback(CallbackResult.SUCCESS, "ok")
            }else{
                callback(CallbackResult.ERROR, "ok")
            }
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

参数说明

  • type:设备类型,以配置文件中的type对应

  • action:设备操作动作

  • callback:回调函数,通过(_ code:Int, _ data:Any?)函数通知web设备调用情况

  • cite:上下文引用,其方法如下

    //应用名称
    public var appName:String

    //应用类型
    public var appType:FXAppType

    //获取文件访问器
    public func getFileAccessor()->FileAccessor

    //获取HTTP访问器
    public func getHttpRequester()->HttpRequester

§ 配置

    <?xml version="1.0" encoding="UTF-8"?>
    <plugins>
        <!--外设模块扩展-->
        <extension point="fox.extension.device">
            <!--OCR测试-->
            <device class="DemoOCRDevice" scope="singleton"
                type="ocr" typeName="OCR" devId="ocr-demo" devName="测试OCR外设"></device>
        </extension>
    </plugins>
1
2
3
4
5
6
7
8
9

配置说明

  • type:设备类型,js就是根据type来调用不同的外设的
  • typeName:设备类型的描述
  • devId:设备的ID必须唯一
  • devName:设备的描述

§ js调用

    //外设测试
    deviceTest:function(){
    //type:配置文件上的type保持一致, action, params, callback
    fox.device.call("ocr","test","外设测试",(code, message ,data)=>{
    //code 0:成功,1:取消,2:失败
    //调用成功后,返回数据在data中,如果失败了则message是失败消息
     if(code == 0){
        fox.layer.open(data);
     }else{
        fox.layer.open("调用失败,"+message);
     }
     });
    }
1
2
3
4
5
6
7
8
9
10
11
12
13

参数说明 fox.device.call(type,action,params,callback)是外设调用的js接口,其参数为

  • type:外设类型和配置文件上的保持一致
  • action:动作,和与原生实现进行约定
  • params:参数
  • callack:回调函数

§ Native开发指南

Native插件用于集成简单的原生功能,下面是例子

§ swift

    //
    //  DemoNetworkNative.swift
    //  app
    //
    //  Created by 江成 on 2020/1/2.
    //
    
    import Foundation
    
    import core
    
    //Demo Network Native
    class DemoNetworkNative: Native{
        
        //native调用
        public override func call(action:String, param:String, cite:Cite, callback:@escaping(_ code:Int, _ data:Any?)->Void){
            //根据js传递过来的动作执行
            if action == "wifiStatus"{
                callback(CallbackResult.SUCCESS, "ok")
            }else{
                callback(CallbackResult.ERROR, "ok")
            }
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

参数说明

  • action:操作动作

  • param:参数

  • callbackContext:回调上下文,通过callback(int code,Object data)函数通知web设备调用情况

  • cite:上下文引用,其方法如下

    //应用名称
    public var appName:String

    //应用类型
    public var appType:FXAppType

    //获取文件访问器
    public func getFileAccessor()->FileAccessor

    //获取HTTP访问器
    public func getHttpRequester()->HttpRequester

§ 配置

    <?xml version="1.0" encoding="UTF-8"?>
    <plugins>
    <!--native接口模块扩展-->
    <extension point="fox.extension.native">
        <native action="wifiStatus" class="DemoNetworkNative">
        </native>
    </extension>
    </plugins>
1
2
3
4
5
6
7
8

配置说明 fox.native.call(action,params,callback)是native调用的js接口,其参数为

  • action:动作,和与原生实现进行约定和配置文件保持一致
  • params:参数
  • callack:回调函数

§ js调用

    //native测试
     nativeTest:function(){
    // action:配置文件上的action保持一致, params, callback
    fox.native.call("wifiStatus","native测试",(code, message ,data)=>{
      //code 0:成功,1:取消,2:失败
      //调用成功后,返回数据在data中,如果失败了则message是失败消息
      if(code == 0){
        fox.layer.open(data);
      else{
        fox.layer.open("调用失败,"+message);
      }
      });
      }
1
2
3
4
5
6
7
8
9
10
11
12
13

§ APlugin开发指南

一个插件的开发主要包括swift/OC、配置和js三部分

§ swift

插件中暴露给JS调用的方法

    //
    //  DemoPlugin.swift
    //  app
    //
    //  Created by 江成 on 2020/1/2.
    //
    
    import Foundation
    import fox_ninetales
    import core
    import XCGLogger
    
    class DemoPlugin: APlugin{
        
        //获取logger
        private lazy var logger = {
            return XCGLogger()
        }()
        
        //初始化
        public required init(){
            super.init()
            self.logger.info("插件初始化,仅会执行一次")
        }
        
        //同步执行
        public override func syncExecute(action:String, args:[Any])->Any?{
            //TODO:同步执行实现
            let message = args[0] as? String ?? "unknown"
            logger.info("同步执行,\(message)")
            return "fox_device_demo_back:\(message)"
        }
        
        //异步步执行
        public override func asyncExecute(action:String, args:[Any], callbackContext:FXCallbackContext){
            //TODO:异步执行实现
            let message = args[0] as? String ?? "unknown"
            logger.info("异步执行,\(message)")
            callbackContext.success(args: "fox_device_demo_back:\(message)")
        }
        
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

§ APlugin便利方法

APlugin提供获取http请求器和文件访问器的方法

//获取文件访问器
public func getFileAccessor(appName:String)->FileAccessor
        
//获取HTTP请求器
public func getHttpRequester(appType:FXAppType, appName:String)->HttpRequester

§ 配置

    <?xml version="1.0" encoding="UTF-8"?>
    <plugins>

    <!--插件扩展-->
    <extension point="fox.extension.plugin">
        <!--测试插件-->
        <plugin name="demoPlugin" class="DemoPlugin" text="测试插件">
        </plugin>
    </extension>
</plugins>
1
2
3
4
5
6
7
8
9
10

§ js插件

    /**
    * Created by 江成
    */
    (function (fox, window, factory) {
    // 判断是否支持模块定义
    let hasDefine = (typeof define === 'function');
    if (hasDefine) {
        //获取对象
        let exports = factory(fox,window);
        //定义模块
        define(exports);
        //安装插件(兼容非模块的访问方式)
        window.fox.demo = exports;
        } else {
        //获取对象
        let exports = factory(fox,window);
        //安装插件
        window.fox.demo = exports;
    }
    }(fox, window, function (fox,window) {
    //定义Demo对象
    let demo={

        /**
         * 同步测试
         * @param message
         */
        syncFn:function(message) {
            //参数
            let args = {
                service: "demoPlugin", //对应FXProxy注册在配置文件上的服务名
                action: "syncFn", //对应FXProxy中标志了@JavascriptInterface的方法
                data: [message],  //对应方法中的参数
                async: false      //是否异步
            };
            return window.fxBridge.exec(args);
        },

        /**
         * 异步测试
         * @param message
         * @param callback
         */
        asyncFn:function(message,callback) {
            //参数
            let args = {
                service: "demoPlugin",//对应FXProxy注册在配置文件上的服务名
                action: "asyncFn",//对应FXProxy中标志了@JavascriptInterface的方法
                data: [message],  //对应方法中的参数
                async: true,      //是否异步
                callback:callback //异步回调函数
            };
            return window.fxBridge.exec(args);
        }

    };
    return demo;
    }));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

§ js调用

    //同步测试
    demoSyncTest:function(){
        let res = fox.demo.syncFn("同步测试");
        fox.layer.open(res);
    },
    //异步测试
    demoASyncTest:function(){
        fox.demo.asyncFn("异步测试",(status,data)=>{
             //status 0:成功,1:取消,2:失败
             if(status == '0'){
                 fox.layer.open(data);
             }else{
                 fox.layer.open("调用失败");
             }
       });
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

§ APlugin插件APP生命周期Hook说明

在APlugin中能监听到APP的整个生命周期,我们在对应的回调中,做某些SDK的初始化或销毁工作

    //MARK:App Delegate
        
        //APP启动
        open func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) {
            //TODO:APP启动处理
        }
        
        //APP暂停
        open func applicationWillResignActive(_ application: UIApplication) {
            //TODO:APP暂停处理
        }
        
        //APP进入后台
        open func applicationDidEnterBackground(_ application: UIApplication) {
            //TODO:APP进入后台处理
        }
        
        //APP进入前台
        open func applicationWillEnterForeground(_ application: UIApplication) {
            //TODO:APP进入前台处理
        }
        
        //APP重新激活
        open func applicationDidBecomeActive(_ application: UIApplication) {
            //TODO:APP重新激活处理
        }
        
        //APP终止
        open func applicationWillTerminate(_ application: UIApplication) {
            //TODO:APP终止处理
        }
        
        //APP openURL事件处理
        open func application(_ application: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool? {
            //TODO:/APP openURL事件处理
            return nil
        }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

§ APlugin插件webview生命周期Hook说明

    //MARK:WKNavigationDelegate
        
        //页面开始加载时调用(返回false代表不处理,true代表已经处理,不会继续传递下一个插件处理)
        open func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) -> Bool{
            //TODO:页面开始加载时调用处理
            return false
        }
        
        //当内容开始返回时调用(返回false代表不处理,true代表已经处理,不会继续传递下一个插件处理)
        open func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) -> Bool{
            //TODO:当内容开始返回时调用处理
            return false
        }
        
        // 页面加载完成之后调用(返回false代表不处理,true代表已经处理,不会继续传递下一个插件处理)
        open func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) -> Bool{
            //TODO:页面加载完成之后调用
            return false
        }
        
        //页面加载失败时调用(返回false代表不处理,true代表已经处理,不会继续传递下一个插件处理)
        open func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error)->Bool{
            //TODO:页面加载失败时调用
            return false
        }
        
        // 接收到服务器跳转请求之后调用(返回false代表不处理,true代表已经处理,不会继续传递下一个插件处理)
        open func webView(_ webView: WKWebView, didReceiveServerRedirectForProvisionalNavigation navigation: WKNavigation!)->Bool{
            //TODO:接收到服务器跳转请求之后调用
            return false
        }
        
        // 在收到响应后,决定是否跳转(返回false代表不处理,true代表已经处理,不会继续传递下一个插件处理)
        open func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) -> Bool{
            //TODO:在收到响应后,决定是否跳转
            return false
        }
        
        // 在发送请求之前,决定是否跳转(返回false代表不处理,true代表已经处理,不会继续传递下一个插件处理)
        open func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) -> Bool{
            //TODO:在发送请求之前,决定是否跳转
            return false
        }
        
        //MARK:WKUIDelegate
        
        //处理网页js中的提示框,若不使用该方法,则提示框无效 (返回false代表不处理,true代表已经处理,不会继续传递下一个插件处理)
        open func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) -> Bool{
            //TODO:处理网页js中的提示框,若不使用该方法,则提示框无效
            return false
        }
        
        //处理网页js中的确认框,若不使用该方法,则确认框无效 (返回false代表不处理,true代表已经处理,不会继续传递下一个插件处理)
        open func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void)-> Bool{
            //TODO:处理网页js中的确认框,若不使用该方法,则确认框无效
            return false
        }
        
        //处理网页js中的文本输入 (返回false代表不处理,true代表已经处理,不会继续传递下一个插件处理)
        open func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Void)->Bool{
            //TODO:处理网页js中的文本输入
            return false
        }
        
        //是否信任服务端的https证书 (返回false代表不处理,true代表已经处理,不会继续传递下一个插件处理)
        open func webView(_ webView: WKWebView, didReceive challenge:URLAuthenticationChallenge, completionHandler: @escaping(URLSession.AuthChallengeDisposition, URLCredential?)->Void)->Bool{
            return false
        }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
最后更新于: 7/5/2022, 5:29:52 PM